﻿using System;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 时间戳工厂类
    /// </summary>
    public class TicksTimestampFactory : ITimestampFactory
    {
        private readonly DateTime _refTime;

        /// <summary>
        /// 参照基准时间时间类型
        /// </summary>
        private readonly DateTimeKind _refTimeKind;

        /// <summary>
        /// 参照基准时间-Ticks
        /// 从0001年1月1日开始计算，单位是100纳秒
        /// </summary>
        private readonly long _refTimeTicks;

        /// <summary>
        /// 参考时间最大值
        /// </summary>
        private readonly long _refTimeMaxTicks;

        /// <summary>
        /// 时间戳单位
        /// </summary>
        private readonly TimestampUnit _timestampUnit;

        /// <summary>
        /// 时间戳精度
        /// </summary>
        private readonly uint _precision;

        /// <summary>
        /// 默认时间戳值是否需要再次进行转换[true:需要;false:不需要]
        /// </summary>
        private readonly bool _hasConvert = false;

        /// <summary>
        /// 参照基准时间对应的时间戳值,即当前支持的最小时间戳
        /// </summary>
        private readonly double _minTimestamp;

        /// <summary>
        /// 参考时间最大值对应的时间戳值,即当前支持的最大时间戳
        /// </summary>
        private readonly double _maxTimestamp;



        #region 属性
        /// <summary>
        /// 获取参照基准时间
        /// </summary>
        public DateTime RefTime
        {
            get { return this._refTime; }
        }


        /// <summary>
        /// 获取时间戳单位
        /// </summary>
        public TimestampUnit TimestampUnit
        {
            get { return this._timestampUnit; }
        }

        /// <summary>
        /// 获取时间戳精度
        /// </summary>
        public uint Precision
        {
            get { return this._precision; }
        }

        /// <summary>
        /// 获取参照基准时间对应的时间戳值,即当前支持的最小时间戳
        /// </summary>
        public double MinTimestamp
        {
            get { return this._minTimestamp; }
        }

        /// <summary>
        /// 获取参考时间最大值对应的时间戳值,即当前支持的最大时间戳
        /// </summary>
        public double MaxTimestamp
        {
            get { return this._maxTimestamp; }
        }
        #endregion



        private const long TicksPerDay = 864000000000L;//天
        private const long TicksPerHour = 36000000000L;//时
        private const long TicksPerMinute = 600000000L;//分
        private const long TicksPerSecond = 10000000L;//秒
        private const long TicksPerMillisecond = 10000L;//毫秒
        private const long TicksPerMicrosecond = 10L;//微秒
        private const long TicksPer100Nanosecond = 1;//百纳秒
        private const long TicksPerNanosecond = 100;//纳秒
        private const long TicksPerPicosecond = 100000;//皮秒



        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="refTime">参考时间</param>
        /// <param name="timestampUnit">时间戳单位,默认毫秒</param>
        /// <param name="precision">时间戳精度</param>
        public TicksTimestampFactory(DateTime refTime, TimestampUnit timestampUnit = TimestampUnit.Millisecond, uint precision = 1)
            : base()
        {
            DateTime maxTime = new DateTime(9999, 12, 31, 23, 59, 59, refTime.Kind);
            this._refTime = refTime;

            this._refTimeTicks = refTime.Ticks;
            this._refTimeMaxTicks = maxTime.Ticks;
            this._refTimeKind = refTime.Kind;
            this._timestampUnit = timestampUnit;
            this._precision = precision;
            this._hasConvert = precision != 1;
            this._minTimestamp = this.PrimitiveGetTimestamp(refTime);
            this._maxTimestamp = this.PrimitiveGetTimestamp(maxTime);
        }




        #region Timestamp

        /// <summary>
        /// 获取指定日期时间的时间戳
        /// </summary>
        /// <param name="dateTime">计算时间戳时间</param>
        /// <returns>当前时间的时间戳</returns>
        private long PrimitiveGetTimestamp(DateTime dateTime)
        {
            long nanosecond100s = dateTime.Ticks - this._refTimeTicks;//单位:百纳秒
            long timestamp;
            switch (this._timestampUnit)
            {
                case TimestampUnit.Day:
                    timestamp = nanosecond100s / TicksPerDay;
                    break;
                case TimestampUnit.Hour:
                    timestamp = nanosecond100s / TicksPerHour;
                    break;
                case TimestampUnit.Minute:
                    timestamp = nanosecond100s / TicksPerMinute;
                    break;
                case TimestampUnit.Second:
                    timestamp = nanosecond100s / TicksPerSecond;
                    break;
                case TimestampUnit.Millisecond:
                    timestamp = nanosecond100s / TicksPerMillisecond;
                    break;
                case TimestampUnit.Microsecond:
                    timestamp = nanosecond100s / TicksPerMicrosecond;
                    break;
                case TimestampUnit.Nanosecond100:
                    timestamp = nanosecond100s / TicksPer100Nanosecond;
                    break;
                case TimestampUnit.Nanosecond:
                    timestamp = nanosecond100s * TicksPerNanosecond;
                    break;
                case TimestampUnit.Picosecond:
                    timestamp = nanosecond100s * TicksPerPicosecond;
                    break;
                default:
                    throw new NotImplementedException(this._timestampUnit.ToString());
            }

            if (this._hasConvert)
            {
                timestamp = timestamp / this._precision;
            }

            return timestamp;
        }

        /// <summary>
        /// 获取当前日期时间的时间戳
        /// </summary>
        /// <returns>当前时间的时间戳</returns>
        public long GetTimestamp()
        {
            return this.PrimitiveGetTimestamp(TimeEx.GetNow(this._refTimeKind));
        }

        /// <summary>
        /// 获取指定日期时间的时间戳
        /// </summary>
        /// <param name="dateTime">计算时间戳时间</param>
        /// <returns>当前时间的时间戳</returns>
        public long GetTimestamp(DateTime dateTime)
        {
            if (dateTime.Kind != this._refTimeKind)
            {
                dateTime = TimeEx.ConvertDateTimeKind(dateTime, this._refTimeKind);
            }

            return this.PrimitiveGetTimestamp(dateTime);
        }

        /// <summary>
        /// 时间戳转换为DateTime
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns>时间</returns>
        public DateTime TimestampToDateTime(long timestamp)
        {
            if (this._hasConvert)
            {
                timestamp = timestamp * this._precision;
            }

            long timestampTicks;
            switch (this._timestampUnit)
            {
                case TimestampUnit.Day:
                    timestampTicks = timestamp * TicksPerDay;
                    break;
                case TimestampUnit.Hour:
                    timestampTicks = timestamp * TicksPerHour;
                    break;
                case TimestampUnit.Minute:
                    timestampTicks = timestamp * TicksPerMinute;
                    break;
                case TimestampUnit.Second:
                    timestampTicks = timestamp * TicksPerSecond;
                    break;
                case TimestampUnit.Millisecond:
                    timestampTicks = timestamp * TicksPerMillisecond;
                    break;
                case TimestampUnit.Microsecond:
                    timestampTicks = timestamp * TicksPerMicrosecond;
                    break;
                case TimestampUnit.Nanosecond100:
                    timestampTicks = timestamp * TicksPer100Nanosecond;
                    break;
                case TimestampUnit.Nanosecond:
                    timestampTicks = timestamp / TicksPerNanosecond;
                    break;
                case TimestampUnit.Picosecond:
                    timestampTicks = timestamp / TicksPerPicosecond;
                    break;
                default:
                    throw new NotImplementedException(this._timestampUnit.ToString());
            }

            long ticks = this._refTimeTicks + timestampTicks;
            if (ticks > this._refTimeMaxTicks)
            {
                throw new ArgumentOutOfRangeException($"无效的时间戳值\"{timestamp}\",超出有效时间范围");
            }

            return new DateTime(ticks, this._refTimeKind);
        }

        /// <summary>
        /// 时间戳转换为DateTime
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <returns>时间</returns>
        public DateTime TimestampToDateTime(double timestamp)
        {
            return this.TimestampToDateTime((long)Math.Ceiling(timestamp));
        }
        #endregion


        /*
        #region TicksTimestamp
        
        /// <summary>
        /// 获取指定时间的Ticks时间戳
        /// </summary>
        /// <param name="dateTime">时间</param>
        /// <param name="timestampUnit">Ticks时间戳单位</param>
        /// <param name="precision">时间戳精度</param>
        /// <returns>Ticks时间戳</returns>
        public double GetTicksTimestampD(DateTime dateTime, TimestampUnit timestampUnit = TimestampUnit.Millisecond, uint precision = 1)
        {
            double timestamp;
            switch (timestampUnit)
            {
                case TimestampUnit.Day:
                case TimestampUnit.Hour:
                case TimestampUnit.Minute:
                case TimestampUnit.Second:
                    long ts = dateTime.Ticks - this._refTimeTicks;
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Day:
                            timestamp = ts.TotalDays;
                            break;
                        case TimestampUnit.Hour:
                            timestamp = ts.TotalHours;
                            break;
                        case TimestampUnit.Minute:
                            timestamp = ts.TotalMinutes;
                            break;
                        case TimestampUnit.Second:
                            timestamp = ts.TotalSeconds;
                            break;
                        default:
                            throw new NotImplementedException(timestampUnit.ToString());
                    }

                    break;
                case TimestampUnit.Millisecond:
                case TimestampUnit.Microsecond:
                case TimestampUnit.Nanosecond:
                case TimestampUnit.Picosecond:
                    double microseconds = (double)dateTime.Ticks * 100;
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Millisecond:
                            timestamp = microseconds / 1e6;
                            break;
                        case TimestampUnit.Microsecond:
                            timestamp = microseconds / 1e3;
                            break;
                        case TimestampUnit.Nanosecond:
                            timestamp = microseconds;
                            break;
                        case TimestampUnit.Picosecond:
                            timestamp = microseconds * 1000;
                            break;
                        default:
                            throw new NotImplementedException(timestampUnit.ToString());
                    }
                    break;
                default:
                    throw new NotImplementedException(timestampUnit.ToString());
            }

            if (precision > 1)
            {
                timestamp = timestamp / precision;
            }

            return timestamp;
        }

        /// <summary>
        /// 获取指定时间的Ticks时间戳
        /// </summary>
        /// <param name="timestampUnit">Ticks时间戳单位</param>
        /// <param name="precision">时间戳精度</param>
        /// <returns>Ticks时间戳</returns>
        public double GetTicksTimestampD(TimestampUnit timestampUnit = TimestampUnit.Millisecond, uint precision = 1)
        {
            return this.GetTicksTimestampD(TimeEx.GetNow(this._refTimeKind), timestampUnit, precision);
        }


        /// <summary>
        /// Ticks时间戳转换为DateTime
        /// </summary>
        /// <param name="timestamp">Ticks时间戳</param>
        /// <param name="timestampUnit">Ticks时间戳单位</param>
        /// <param name="dateTimeKind">时间类型,默认类型为本地时间</param>
        /// <param name="precision">时间戳精度</param>
        /// <returns>DateTime</returns>
        public DateTime TicksTimestampToDateTime(double timestamp, TimestampUnit timestampUnit, DateTimeKind dateTimeKind, uint precision)
        {
            DateTime time;
            switch (timestampUnit)
            {
                case TimestampUnit.Day:
                case TimestampUnit.Hour:
                case TimestampUnit.Minute:
                case TimestampUnit.Second:
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Day:
                            time = this._refTimeTicks.AddDays(timestamp);
                            break;
                        case TimestampUnit.Hour:
                            time = this._refTimeTicks.AddHours(timestamp);
                            break;
                        case TimestampUnit.Minute:
                            time = this._refTimeTicks.AddMinutes(timestamp);
                            break;
                        case TimestampUnit.Second:
                            time = this._refTimeTicks.AddSeconds(timestamp);
                            break;
                        default:
                            throw new NotSupportedException(timestampUnit.ToString());
                    }
                    break;
                case TimestampUnit.Millisecond:
                case TimestampUnit.Microsecond:
                case TimestampUnit.Nanosecond:
                case TimestampUnit.Picosecond:
                    timestamp = timestamp * precision;
                    double microseconds;
                    switch (timestampUnit)
                    {
                        case TimestampUnit.Millisecond:
                            microseconds = timestamp * 1e6;
                            break;
                        case TimestampUnit.Microsecond:
                            microseconds = timestamp * 1e3;
                            break;
                        case TimestampUnit.Nanosecond:
                            microseconds = timestamp;
                            break;
                        case TimestampUnit.Picosecond:
                            microseconds = timestamp / 1000;
                            break;
                        default:
                            throw new NotImplementedException(timestampUnit.ToString());
                    }

                    long ticks = (long)(microseconds / 100);
                    time = new DateTime(ticks, this._refTimeTicks.Kind);
                    break;
                default:
                    throw new NotImplementedException(timestampUnit.ToString());
            }

            if (time.Kind != dateTimeKind)
            {
                time = TimeEx.ConvertDateTimeKind(time, dateTimeKind);
            }

            return time;
        }

      
        #endregion

        */
    }
}
